package com.tools.web.email;

import com.sun.mail.imap.IMAPStore;
import com.tools.common.container.MapEnum;
import com.tools.common.exception.MailOperationException;
import com.tools.common.exception.ParseRuntimeException;
import com.tools.common.io.CharsetEnum;
import com.tools.common.io.ProfileKit;
import com.tools.common.object.Note;
import com.tools.common.object.string.StrKit;
import com.tools.web.http.UploadFile;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.search.SearchTerm;
import javax.mail.util.ByteArrayDataSource;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Properties;

/**
 * 邮件操作工具类
 * */
@Note("邮件操作工具类")
public final class MailKit {

    private MailKit() {}

    @Note("默认的用于发送邮件的 Session 对象。多个线程可以共用一个 Session，这是线程安全的")
    private static volatile Session DEFAULT_SESSION;

    @Note("配置文件中默认设置的配置项参数实例")
    private static MailSource DEFAULT_MAIL_SOURCE;



    /* **********************************************************************************
     *
     *          成品方法，直接调用即可
     *
     * *********************************************************************************
     * */


    @Note("【成品方法】: 使用 MailSource 邮箱构造对象构造 Session 对象，搭配邮件构造对象 MailBuilder 构建邮件，然后发送")
    public static void send(MailSource mailSource, MailBuilder builder) {
        Session session = newSession(mailSource);
        send(session, builder, mailSource);
    }


    @Note("【成品方法】: 使用指定的 Session 和邮箱构造对象、搭配邮件构造对象 MailBuilder 构建邮件，然后发送")
    public static void send(Session session, MailBuilder builder, MailSource mailSource) {
        Transport transport = newTransport(session, mailSource);
        Message message = newMessage(session, builder, mailSource);
        executeSend(transport, message, mailSource);
    }


    @Note("【成品方法】: 使用指定 Session 对象获取指定邮箱账号的收件箱的所有[未读]邮件，返回一个 MailFolder 对象")
    public static MailFolder readUnread(Session session, MailSource mailSource) {
        private_checkMailSourceReceiveInfo(mailSource);
        IMAPStore store = getIMAPStore(session);
        try {
            connect(store, mailSource);
            Folder folder = getFolder(store, "INBOX");
            return new MailFolder(folder);
        } finally {
            close(store);
        }
    }


    @Note("【成品方法】: 使用指定 Session 对象获取指定邮箱账号的收件箱的[所有邮件]，返回一个 MailFolder 对象")
    public static MailFolder readINBOXAll(Session session, MailSource mailSource) {
        return readAll(session, mailSource, "INBOX");
    }


    @Note("【成品方法】: 使用指定 Session 对象获取指定邮箱账号的[某个邮件夹里的所有邮件]，返回一个 MailFolder 对象")
    public static MailFolder readAll(Session session, MailSource mailSource, String folderName) {
        private_checkMailSourceReceiveInfo(mailSource);
        IMAPStore store = getIMAPStore(session);
        try {
            connect(store, mailSource);
            Folder folder = getFolder(store, folderName);
            return new MailFolder(folder);
        } finally {
            close(store);
        }
    }

    @Note("【成品方法】: 使用指定 Session 对象获取指定邮箱账号下某个邮件夹里[所有与 SearchTerm 相匹配的邮件]，返回一个 MailFolder 对象")
    public static MailFolder read(Session session, MailSource mailSource, String folderName, SearchTerm searchTerm) {
        private_checkMailSourceReceiveInfo(mailSource);
        IMAPStore store = getIMAPStore(session);
        try {
           connect(store, mailSource);
           Folder folder = getFolder(store, folderName);
           return new MailFolder(folder, searchTerm);
        } finally {
           close(store);
        }
    }


    @Note("【成品方法】: 使用指定 Session 对象获取指定邮箱账号的收件箱里[所有与 SearchTerm 相匹配的邮件]，返回一个 MailFolder 对象")
    public static MailFolder readINBOX(Session session, MailSource mailSource, SearchTerm searchTerm) {
        return read(session, mailSource, "INBOX", searchTerm);
    }


    @Note("使用指定的 Session 对象和指定的 MailSource 包装的邮箱账户信息，回复 sourceMsg 的邮件的方法。" +
            "replyToAll 为 true 则回复给发件人、抄送人和密送人等所有人。false 只回复发件人")
    public static void reply(Session session, Message sourceMsg, boolean replyToAll, MailBuilder builder, MailSource mailSource) {
        MimeMessage replyMessage = (MimeMessage) replyMessage(sourceMsg, replyToAll);
        private_checkMailSourceSendInfo(mailSource);
        private_checkMailBuilder(builder);
        try {
            InternetAddress address = private_newServerAddress(mailSource, builder.getEncoder());
            sourceMsg.setFrom(address);
            private_fillInMessageData(sourceMsg, builder);
            replyMessage.setReplyTo(sourceMsg.getReplyTo());
            Transport transport = newTransport(session, mailSource);
            executeSend(transport, replyMessage, mailSource);
        } catch (Exception e) {
            throw new MailOperationException(e);
        }
    }

    /* **********************************************************************************
     *
     *          默认的成品方法
     *
     * *********************************************************************************
     * */

    @Note("【成品方法】: 使用默认 Session 和邮箱构造对象，搭配邮件构造对象 MailBuilder 构建邮件，然后发送")
    public static void send(MailBuilder builder) {
        private_lazyLoadSession();
        send(DEFAULT_SESSION, builder, DEFAULT_MAIL_SOURCE);
    }


    @Note("【成品方法】: 使用默认 Session 对象获取收件箱的所有[未读]邮件，返回一个 MailFolder 对象")
    public static MailFolder readUnread() {
        private_lazyLoadSession();
        return readUnread(DEFAULT_SESSION, DEFAULT_MAIL_SOURCE);
    }


    @Note("【成品方法】: 使用默认 Session 对象获取收件箱的[所有邮件]，返回一个 MailFolder 对象")
    public static MailFolder readINBOXAll() {
        private_lazyLoadSession();
        return readINBOXAll(DEFAULT_SESSION, DEFAULT_MAIL_SOURCE);
    }


    @Note("【成品方法】: 使用默认 Session 对象获取[某个邮件夹的所有邮件]，返回一个 MailFolder 对象")
    public static MailFolder readAll(String folderName) {
        private_lazyLoadSession();
        return readAll(DEFAULT_SESSION, DEFAULT_MAIL_SOURCE, folderName);
    }


    @Note("【成品方法】: 使用默认 Session 对象，根据 SearchTerm 的条件[获取收件箱里与之匹配的邮件]，返回一个 MailFolder 对象")
    public static MailFolder readINBOX(SearchTerm searchTerm) {
        private_lazyLoadSession();
        return readINBOX(DEFAULT_SESSION, DEFAULT_MAIL_SOURCE, searchTerm);
    }


    @Note("【成品方法】: 使用默认 Session 对象，根据 SearchTerm 的条件[获取指定邮件夹里与之匹配的邮件]，返回一个 MailFolder 对象")
    public static MailFolder read(String folderName, SearchTerm searchTerm) {
        private_lazyLoadSession();
        return read(DEFAULT_SESSION, DEFAULT_MAIL_SOURCE, folderName, searchTerm);
    }


    @Note("使用默认的 Session 对象和默认的 MailSource 包装的邮箱账户信息，回复 sourceMsg 的邮件的方法")
    public static void reply(Message sourceMsg, boolean replyToAll, MailBuilder builder) {
        private_lazyLoadSession();
        reply(DEFAULT_SESSION, sourceMsg, replyToAll, builder, DEFAULT_MAIL_SOURCE);
    }

    /* **********************************************************************************
     *
     *          发送操作
     *
     * *********************************************************************************
     * */


    @Note("根据指定实例的参数项配置构建一个用于发送邮箱的 Session 对象出来")
    public static Session newSession(MailSource mailSource) {
        private_checkMailSourceSendInfo(mailSource);
        Properties properties = private_newSSLConfig(mailSource);
        String sendMailer = mailSource.getSendMailer();
        String authCode = mailSource.getAuthCode();
        Session session = Session.getDefaultInstance(properties, new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(sendMailer, authCode);
            }
        });
        session.setDebug(mailSource.isEnableDebug());
        return session;
    }

    @Note("从 Session 对象中获取一个用于连接传输邮件的对象")
    public static Transport newTransport(Session session, MailSource mailSource) {
        try {
            return session.getTransport();
        } catch (NoSuchProviderException e) {
            throw new MailOperationException(e);
        }
    }



    @Note("使用邮件实例、邮箱参数实例、Session 对象共同创建一封邮件，里面包括装备了附件、文本等等信息")
    public static Message newMessage(Session session, MailBuilder builder, MailSource mailSource) {
        private_checkMailSourceSendInfo(mailSource);
        private_checkMailBuilder(builder);
        MimeMessage message = new MimeMessage(session);
        try{
            InternetAddress address = private_newServerAddress(mailSource, builder.getEncoder());
            message.setFrom(address);
            private_fillReceiversIntoMessage(message, builder);
            private_fillInMessageData(message, builder);
        } catch (Exception e) {
            throw new MailOperationException(e);
        }
        return message;
    }



    @Note("连接 MailSource 里配置的邮箱地址，然后用它来发送 Message 邮件给所有的接收方邮箱")
    public static void executeSend(Transport transport, Message message, MailSource mailSource) {
        private_checkMailSourceSendInfo(mailSource);
        try{
            transport.connect(mailSource.getSendServerHost(), mailSource.getSendMailer(), mailSource.getAuthCode());
            transport.sendMessage(message, message.getAllRecipients());
        } catch (Exception e) {
            throw new MailOperationException(e);
        } finally {
            close(transport);
        }
    }



    /* **********************************************************************************
     *
     *          关闭操作
     *
     * *********************************************************************************
     * */



    @Note("关闭邮件传输对象")
    public static void close(Transport transport) {
        if(transport != null) {
            try {
                transport.close();
            } catch (MessagingException e) {
                throw new MailOperationException(e);
            }
        }
    }


    @Note("关闭邮件箱对象")
    public static void close(Folder folder, boolean set) {
        if(folder != null) {
            try {
                folder.close(set);
            } catch (MessagingException e) {
                throw new MailOperationException(e);
            }
        }
    }


    @Note("关闭邮件箱对象")
    public static void close(Folder folder) {
        if(folder != null) {
            try {
                folder.close();
            } catch (MessagingException e) {
                throw new MailOperationException(e);
            }
        }
    }


    @Note("关闭邮箱仓库对象")
    public static void close(Store store) {
        if(store != null) {
            try {
                store.close();
            } catch (MessagingException e) {
                throw new MailOperationException(e);
            }
        }
    }

    /* **********************************************************************************
     *
     *          接收操作
     *
     * *********************************************************************************
     * */


    @Note("用指定的 Session 对象获取 imap 协议的邮箱仓库对象")
    public static IMAPStore getIMAPStore(Session session) {
        try {
            return (IMAPStore) session.getStore();
        } catch (NoSuchProviderException e) {
            throw new MailOperationException(e);
        }
    }



    @Note("用本类默认的 Session 对象获取 imap 协议的邮箱仓库对象")
    public static IMAPStore getIMAPStore() {
        private_lazyLoadSession();
        return getIMAPStore(DEFAULT_SESSION);
    }

    @Note("让 imap 协议的邮箱仓库对象，连接指定邮箱账户的邮箱")
    public static void connect(IMAPStore store, MailSource mailSource) {
        try {
            store.connect(mailSource.getSendMailer(), mailSource.getAuthCode());
            //如果是网易邮箱这部分是必须的，否则会抛异常。其他邮箱可有可无也就放在这了
            HashMap<String, String> IAM = new HashMap<>(MapEnum.calcInitSize(4));
            IAM.put("name", "Dawn");
            IAM.put("version", "1.0.0");
            IAM.put("vendor", "dawn's client");
            IAM.put("support-email", "testmail@test.com");
            store.id(IAM);
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("从指定邮箱账户的邮箱仓库里获取指定名称的邮件夹")
    public static Folder getFolder(Store store, String name) {
        try {
            return store.getFolder(name);
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("从指定邮箱账户的邮箱仓库里获取收件箱对象")
    public static Folder getINBOXFolder(Store store) {
        try {
            return store.getFolder("INBOX");
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("从指定邮箱账户的邮箱仓库里获取发件箱对象")
    public static Folder getOUTBOXFolder(Store store) {
        try {
            return store.getFolder("OUTBOX");
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("用指定的方式打开邮件夹")
    public static void openFolder(Folder folder, int openCode) {
        if(folder.isOpen()) return;
        if(openCode != 1 && openCode != 2) throw new IllegalArgumentException("邮件箱 open 的方式参数有误，建议 Folder.xxx 获取正确参数");
        try {
            folder.open(openCode);
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("获取指定邮件夹下的所有邮件")
    public static Message[] getAllMessages(Folder folder) {
        if(folder.isOpen()) {
            try {
                return folder.getMessages();
            } catch (MessagingException e) {
                throw new MailOperationException(e);
            }
        }
        throw new IllegalStateException("Folder 邮件箱尚未 open");
    }


    @Note("获取指定邮件夹下，符合 searchTerm 条件的所有邮件。" +
            "SearchTerm 的使用: https://blog.csdn.net/miaoy220/article/details/52413157")
    public static Message[] searchMessages(Folder folder, SearchTerm searchTerm) {
        try {
            return folder.search(searchTerm);
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("根据邮件的编号获取指定邮件夹下的某个邮件")
    public static Message getMessage(Folder folder, int msgIndex) {
        if(msgIndex < 0) throw new IllegalArgumentException("请输入正确的邮件索引");
        if(folder.isOpen()) {
            try {
                return folder.getMessage(msgIndex);
            } catch (MessagingException e) {
                throw new MailOperationException(e);
            }
        }
        throw new IllegalStateException("Folder 邮件箱尚未 open");
    }


    @Note("获取邮件夹下所有邮件的总数")
    public static int getMessageCount(Folder folder) {
        try {
            return folder.getMessageCount();
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("获取邮件夹下所有未读邮件的总数")
    public static int getUnreadMessageCount(Folder folder) {
        try {
            return folder.getUnreadMessageCount();
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("获取邮件夹下所有新邮件的总数")
    public static int getNewMessageCount(Folder folder) {
        try {
            return folder.getNewMessageCount();
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("获取邮件夹下所有已删除的邮件的总数")
    public static int getDeletedMessageCount(Folder folder) {
        try {
            return folder.getDeletedMessageCount();
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }


    @Note("回复 message 邮件，获取一个新的回复邮件实例" +
            "replyToAll 为 true 则回复给发件人、抄送人和密送人等所有人。false 只回复发件人")
    public static Message replyMessage(Message message, boolean replyToAll) {
        try {
            return message.reply(replyToAll);
        } catch (MessagingException e) {
            throw new MailOperationException(e);
        }
    }

    /* **********************************************************************************
     *
     *          私有逻辑
     *
     * *********************************************************************************
     * */

    @Note("发送邮件之前的校验，检测构建的 Session 对象的必备参数项")
    private static void private_checkMailSourceSendInfo(MailSource mailSource) {
        if(mailSource == null)  throw new NullPointerException("邮箱配置包装对象 MailSource 不允许为 null");
        if(StrKit.isAnyBlank(mailSource.getSendMailer(), mailSource.getSendServerHost(), mailSource.getAuthCode())) {
            throw new IllegalArgumentException("用于发送邮件的三大参数配置项（发送邮件的账户、发邮件的服务器地址、授权码）不允许为空");
        }
    }


    @Note("接收邮件之前的校验，检测打开邮箱仓库 Store 对象的必备参数项")
    private static void private_checkMailSourceReceiveInfo(MailSource mailSource) {
        if(mailSource == null) throw new NullPointerException("邮箱配置包装对象 MailSource 不允许为 null");
        if(StrKit.isAnyBlank(mailSource.getReceiveMailer(), mailSource.getAuthCode(), mailSource.getReceiveServerHost())) {
            throw new IllegalArgumentException("用于接收邮件的三大参数配置项（接收邮件的账户、收邮件的服务器地址、授权码）不允许为空");
        }
    }


    @Note("设置构建发送邮箱的 Session 对象的 SSL 连接等参数项装配，并返回一个 Properties")
    private static Properties private_newSSLConfig(MailSource mailSource) {
        Properties properties = new Properties();
        //附件的长参数名称不要截断，主要针对 Linux 系统
        System.setProperty("mail.mime.splitlongparameters", "false");
        //发送邮件的邮箱协议，默认是 smtp
        properties.setProperty("mail.transport.protocol", mailSource.getSendProtocol().getName());
        //该客户端是否向邮箱运营商的服务器提供验证，要设置为 true
        properties.setProperty("mail.smtp.auth", "true");
        //用于发送邮件的服务器地址
        properties.setProperty("mail.smtp.host", mailSource.getSendServerHost());
        properties.setProperty("mail.smtp.starttls.enable", "true");
        //接收邮件的邮箱协议，默认是 imap
        properties.setProperty("mail.store.protocol", mailSource.getReceiveProtocol().getName());
        properties.setProperty("mail.imap.auth", "true");
        //用于接收邮件的服务器地址
        String receiveServerHost = mailSource.getReceiveServerHost();
        if(receiveServerHost != null && !receiveServerHost.isEmpty()) {
            properties.setProperty("mail.imap.host", receiveServerHost);
        }
        //接收邮件时是否开启 SSL 验证
        properties.setProperty("mail.imap.ssl.enable","true");
        properties.setProperty("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        return properties;
    }


    @Note("懒加载默认的 Session 对象，这需要你在外部的配置文件中配置相应的参数项")
    private static void private_lazyLoadSession() {
        if(DEFAULT_SESSION != null) return;
        synchronized (MailKit.class) {
            if(DEFAULT_SESSION != null) return;
            MailSource mailSource = ProfileKit.getMailSource();
            DEFAULT_SESSION = newSession(mailSource);
            DEFAULT_MAIL_SOURCE = mailSource;
        }
    }


    @Note("校验邮件构建对象的各种属性参数是否合理")
    private static void private_checkMailBuilder(MailBuilder builder) {
        if(builder == null) throw new NullPointerException("邮件信息构建对象 MailBuilder 不能为 null");
        LinkedHashSet<MailUser> receivers = builder.getReceiverMails();
        if(receivers == null || receivers.isEmpty()) {
            throw new IllegalArgumentException("你至少需要在 MailBuilder 对象里传入一个以上的接收方 MailUser");
        }
        String subject = builder.getSubject();
        if(subject == null || subject.isEmpty()) {
            throw new IllegalArgumentException("不允许发送空的邮件主题");
        }
        String content = builder.getContent();
        List<UploadFile> attachments = builder.getAttachments();
        if((content == null || content.isEmpty()) && (attachments == null || attachments.isEmpty())) {
            throw new IllegalArgumentException("不允许发送空的邮件内容，至少要有文本或者附件");
        }
    }

    @Note("将自己作为服务器的邮箱地址、别名获取出来，与字符集编码一起构建一个网络地址实例")
    private static InternetAddress private_newServerAddress(MailSource mailSource, CharsetEnum encoder) {
        try {
            String alias = mailSource.getSendAlias();
            if(StrKit.isBlank(alias)) {
                alias = "未知用户";
            }
            return new InternetAddress(mailSource.getSendMailer(), alias, encoder.getValue());
        } catch (UnsupportedEncodingException e) {
            throw new ParseRuntimeException(e);
        }
    }

    @Note("用接收方的邮箱地址、字符集编码一起构建一个网络地址实例")
    private static InternetAddress private_newClientAddress(MailUser mailUser) {
        try {
            return new InternetAddress(mailUser.getMail());
        } catch (AddressException e) {
            throw new ParseRuntimeException(e);
        }
    }


    @Note("将所有的接收方邮箱对象，一一设置到邮件实例 Message 里")
    private static void private_fillReceiversIntoMessage(Message message, MailBuilder builder) throws Exception {
        LinkedHashSet<MailUser> receivers = builder.getReceiverMails();
        for(MailUser mailUser : receivers){
            if(mailUser.getMethod() == MailSendEnum.CC){
                //抄送
                message.setRecipient(Message.RecipientType.CC,
                        private_newClientAddress(mailUser));
                continue;
            }
            if(mailUser.getMethod() == MailSendEnum.BCC){
                //密送
                message.setRecipient(MimeMessage.RecipientType.BCC,  private_newClientAddress(mailUser));
                continue;
            }
            message.setRecipient(MimeMessage.RecipientType.TO,  private_newClientAddress(mailUser));
        }
    }


    @Note("将标题、文本内容、附件等等一起设置进邮件 Message 里")
    private static void private_fillInMessageData(Message message, MailBuilder builder) throws Exception {
        List<UploadFile> attachments = builder.getAttachments();
        //没有附件
        if(attachments == null || attachments.isEmpty()) {
            //是回复别人的邮件
            if(builder.getIsReply() && !builder.isReplaceSubject()) {
                message.setSubject(message.getSubject() + builder.getSubject());
                message.setContent(builder.getContent(), "text/html;charset=" + builder.getEncoder().getValue());
                return;
            }
            //是发送新邮件
            message.setSubject(builder.getSubject());
            message.setContent(builder.getContent(), "text/html;charset=" + builder.getEncoder().getValue());
            return;
        }
        //文本内容
        Multipart multipart = new MimeMultipart();
        BodyPart part = new MimeBodyPart();
        part.setContent(builder.getContent(), "text/html;charset=" + builder.getEncoder().getValue());
        multipart.addBodyPart(part);
        //附件处理
        String encoder = builder.getEncoder().getValueLowerCase();
        for (UploadFile attachment : attachments) {
            BodyPart filePart = private_handleOneAttachment(attachment, encoder);
            multipart.addBodyPart(filePart);
        }
        message.setContent(multipart);
        //主题
        if(builder.getIsReply() && !builder.isReplaceSubject()) {
            message.setSubject(message.getSubject() + builder.getSubject());
            return;
        }
        message.setSubject(builder.getSubject());
    }


    @Note("处理单个附件")
    private static BodyPart private_handleOneAttachment(UploadFile attachment, String encoder) throws Exception {
        InputStream inputStream = attachment.getInputStream();
        if(inputStream == null) {
            throw new NullPointerException("附件的字节输入流为 null，请上传正确的附件");
        }
        String type = attachment.getContentType() + ";charset=" + encoder;
        DataSource source = new ByteArrayDataSource(inputStream, type);
        BodyPart filePart = new MimeBodyPart();
        filePart.setDataHandler(new DataHandler(source));
        String filename = MimeUtility.encodeWord(attachment.getFilename());
        filePart.setFileName(filename);
        return filePart;
    }



}
